import java.util.ArrayList;
import java.util.regex.*;

/**
 *
 * @author Beatris
 */
public class Instruction 
{
   
	static int count = 0;
	
	int myCount = 0;
	
    String name;
    String Written;
    String Read1;
    String Read2;
    String Offset;
    String branch;
    boolean Control;
    boolean label;
    boolean meta;
    int regs;
    int branchIndex;
    
    
    ArrayList<Dependency> dependencies;
        

    public Instruction()
    {
        //default constructor - just create a handle
    }
    
    public Instruction(String line)
    {
    	
    	dependencies = new ArrayList<Dependency>();
    	
    	myCount = count;
    	count++;
		
        line = line.trim(); //eliminate leading and trailing spaces
        //split the instruction line by comas or spaces
        String [] instr = line.split("[\\s,]+");
        String [] ops;
        /*
         * 
         */
        String opCode = instr[0].toUpperCase();
        name = opCode;
        Offset = "";
		if (opCode.length() > 0){
			meta = false;
				if (opCode.charAt(0) == '#'){
					name = "!";
					Written = "";
					Read1 = "";
					Read2 = "";
					Control = false;
					label = false;
					regs = Const.NOREAD_NOWRITE;
				return;
				}			
				if (opCode.charAt(opCode.length() - 1) == ':'){
					//Handle Label
					name = line;//instr[0];
					Written = "";
					Read1 = "";
					Read2 = "";
					Control = false;
					label = true;
					regs = Const.NOREAD_NOWRITE;
					return;
				}
				if (opCode.charAt(0) == '.'){
					name = line;//instr[0];
					Written = "";
					Read1 = "";
					Read2 = "";
					Control = false;
					label = true;
					meta = true;
					regs = Const.NOREAD_NOWRITE;
					return;
				}
		}
     /* The only problem doing it this way (if statements below) is that 
     * if a typo in the instruction is enterred the program would work 
                 * incorrectly.
     * E.g: Let's say I read: ADD $1, $2, 6
     * This is not correct because ADD should not allow immediate values
     * So the value of Read2 will be storred as 6, when this is not a 
     * valid register value
     */
        /*instr $r1, $r2, $r3*/
        if (    opCode.equals("ADD") || opCode.equals("ADDU") || 
                opCode.equals("AND") || opCode.equals("OR") ||
                opCode.equals("SLT") || opCode.equals("SLTU") ||
                opCode.equals("SUB") || opCode.equals("SUBU") ||
                opCode.equals("XOR") || opCode.equals("SLLV") ||
                opCode.equals("SRLV") || opCode.equals("NOR") ||
                opCode.equals("ADD.S") || opCode.equals("SUB.S") ||
                opCode.equals("MUL.S") || opCode.equals("ADD.D") ||
                opCode.equals("SUB.D") || opCode.equals("MUL.D") ||
                opCode.equals("DUV.D")
                )
        {
          
            regs = Const.READ_TWO_WRITE;
            this.Control = false;
            Written = instr[1];
            Read1 = instr[2];
            Read2 = instr[3];
            
        }
        /*instr $r1, $r2, imm*/
        else  if (  opCode.equals("ADDI") || opCode.equals("ADDIU") || 
                    opCode.equals("ANDI") || opCode.equals("ORI") ||
                    opCode.equals("SLTI") || opCode.equals("SLTIU") ||
                    opCode.equals("XORI") || opCode.equals("SLL") ||
                    opCode.equals("SRA") || opCode.equals("SRL")
                )
        {
            regs = Const.READ_ONE_WRITE;
            this.Control = false;
            Written = instr[1];
            Read1 = instr[2];
            Read2 = instr[3];//"";            
            
        }
        /*instr $s, $t, offset*/
        else if (opCode.equals("BNE") || opCode.equals("BEQ"))
        {
            regs = Const.READ_TWO_NOWRITE;
            this.Control = true;
            Written = "";
            Read1 = instr[1];
            Read2 = instr[2]; 
            //the extra colon is to agree with the label
            branch = instr[3] + ":";
            branchIndex = 3;
            branch = instr[3];
            branchIndex = 3;
        }
        
        /*instr $r1, $r2*/
        /*Here the internal $LO is written, not sure whether
                            we should keep track of that.
         * For now, I am not keeping track and just setting that only
         * two registers are read but no writes are done.
         */
        else if (
                    opCode.equals("DIV") || opCode.equals("DIVU") ||
                    opCode.equals("MULT") || opCode.equals("MULTU") ||
                    opCode.equals("C.LT.D") || opCode.equals("C.LT.S")
                )
        {
            regs = Const.READ_TWO_NOWRITE;
            this.Control = false;
            Written = ""; 
            Read1 = instr[1];
            Read2 = instr[2];    
        }        
        
        /*instr $r1, $r2
         * Note: also included "MOVE" - PSEUDO Instruction
         */
        else if (opCode.equals("MFCZ") || opCode.equals("MTCZ")
                || opCode.contains("MOVE"))
        {
            regs = Const.READ_ONE_WRITE;
            this.Control = false;
            Written = instr[1];
            Read1 = instr[2];
            Read2 = "";    
        }        
        
        /* instr $r1, offset */
         else if (
                    opCode.equals("BGEZ") || opCode.equals("BGEZAL") ||
                    opCode.equals("BGTZ") || opCode.equals("BLTZ") ||
                    opCode.equals("BLTZAL")
                )
        {
            regs = Const.READ_ONE_NOWRITE;
            this.Control = true;
            Written = ""; 
            Read1 = instr[1];
            Read2 = "";   
            //the extra colon is to agree with the label
            branch = instr[2] + ":";
            branchIndex = 2;
        }    
        
        /*instr $r1, imm*/
        /*Note: LI - Pseudo instruction*/
        else if (opCode.equals("LUI") || opCode.equals("LI") ||
                 opCode.contains("LA")
                )
        {
            regs = Const.NOREAD_WRITE;
            this.Control = false;
            Written = instr[1];
            Read1 = "";
            Read2 = instr[2];//"";    
        } 
        
        /*instr $r1, offset($r2)*/
        else if (
                    opCode.equals("LB") || opCode.equals("LBU") ||
                    opCode.equals("LW") || opCode.equals("LD") ||
                    opCode.equals("LH") || opCode.equals("LHU") ||
                    opCode.equals("LWCZ")
                )
        {
            regs = Const.READ_ONE_WRITE;
            this.Control = false;
            Written = instr[1];
            //extract anything between the '(' ')' using REGEX                    
            Pattern p = Pattern.compile("\\((.*?)\\)", Pattern.DOTALL); 
            Matcher m = p.matcher(line);
            if(m.find())
            {
                    Read2 = m.group(1);
            }
            else Read2 = "";          
            Read1 = "";    
 			//0x0000, 0
 			if (instr.length> 2)
 			if (instr[2].indexOf("(") > -1)Offset = instr[2].substring(0,instr[2].indexOf("("));  
        }         
        
        /*instr $r1, offset($r2)*/
        else if (
                    opCode.equals("SB") || opCode.equals("SW") ||
                    opCode.equals("SD") || opCode.equals("SH") ||
                    opCode.equals("SWCZ")
                )
        {
            regs = Const.READ_TWO_NOWRITE;
            this.Control = false;
            Written = "";
            Read1 = instr[1];
            //extract anything between the '(' ')' using REGEX
            Pattern p = Pattern.compile("\\((.*?)\\)", Pattern.DOTALL); 
            Matcher m = p.matcher(line);
            if(m.find())
            {
                    Read2 = m.group(1);
            }
            else
                    Read2 = "";
			if (instr.length >= 2)
			if (instr[2].indexOf("(")>-1) Offset = instr[2].substring(0,instr[2].indexOf("("));                            
    
        }  
        
        /*instr $r1*/
        /* Note: Here the internal registers $HI or $LO are read
         *       - For now I am not including them as being read
         */
        else if (
                    opCode.equals("MFHI") || opCode.equals("MFLO")
                )
        {
            regs = Const.NOREAD_WRITE;
            this.Control = false;
            Written = instr[1];
            Read1 = "";
            Read2 = "";    
        }  
        
        /*instr label */
        else if (opCode.equals("BCZT") || opCode.equals("BCZF"))
        {
            regs = Const.NOREAD_NOWRITE;
            this.Control = true;
            Written = "";
            Read1 = "";
            Read2 = instr[1];//"";
        }
        
        /*instr*/
        else if (opCode.equals("NOOP") || opCode.equals("SYSCALL"))
        {
            regs = Const.NOREAD_NOWRITE;
            this.Control = false;
            Written = "";
            Read1 = "";
            Read2 = "";
        }
        
        /*JUMP INSTRUCTIONS*/
        /*j label
         *jal label
         *j $r1
         * 
         * Note: Depending on how we wanna handle Jumps, the
         * assignments below might need to be changed.
         */
        else if (opCode.equals("J") || opCode.equals("JAL") ||
                 opCode.equals("JR"))
        {
            regs = Const.NOREAD_NOWRITE;
            this.Control = false;
            Written = "";
            Read1 = "";
            Read2 = instr[1]; //"";
            
        }
        else
        {
        	//it's not an instruction, or it's one we don't support atm!
        	name = "!";
            regs = Const.NOREAD_NOWRITE;
            this.Control = false;
            Written = "";
            Read1 = "";
            Read2 = "";
        }
                
    }
    
    public String getInfo(String line)
    {
        String out = name + "\tWritten: " + Written + "\tRead1: " + Read1 +
                "\tRead2: " + Read2 + "\tControl: " + Control;
       
        
        return out;
    }
    
    public int getNumReads()
    {
    	int temp = 0;
    	
    	if(Read1 != "")
    		temp++;
    	if(Read2 != "")
    		temp++;
    	
    	return temp;
    }
    
    public boolean reads(String reg)
    {
    	
    	return (Read1.equals(reg) || Read2.equals(reg));
    }
    
    public boolean writes(){
    	if (regs == Const.READ_ONE_WRITE || regs == Const.READ_TWO_WRITE || 
                regs == Const.NOREAD_WRITE) return true;
    	return false;
    }
    
	public String toString(){
		
		String temp = new String("");
		if (!Written.equals("") && !Read1.equals("")){
			temp = Written + ", " + Read1;
		}
		else{
			if (Written.equals("") && Read1.equals("")){
				temp = "";
			}
			if (Written.equals("") && !Read1.equals("")){
				temp = Read1;
			}
			if (!Written.equals("") && Read1.equals("")){
				temp = Written;
			}		
		}
		if (Read2.equals("") == false){
			if (temp.equals("") == false) temp += ", ";
			if (!Offset.equals(""))	temp += Offset + "(" + 
                                Read2 + ")";
			else temp += ((!Read2.equals(""))? Read2: "");
		}
		temp = name + " " + temp;
		if (branchIndex == 3) temp += "," + branch;		
		
		return /*myCount + ": " + */ temp;
	}
}



